/* Garbage Collectors
 * TCSS 360 Winter 2012
 * PersistantStorage.java 
 */

package controller;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.util.Date;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerFactoryConfigurationError;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;

import model.Job;
import model.Park;
import model.Role;
import model.User;
import model.WorkLoad;

/**
 * This class loads and stores persistent data.
 * @author Ben Krack
 * @version Winter 2012
 *
 */
public class PersistentStorage {
  
  /**
   * Loads persisted data.
   *
   * @param the_parks A list of the parks in the system to load into.
   * @param the_users A list of the users in the system to load into.
   * @param the_jobs A list of the jobs in the system to load into.
   * @param the_options The program options to load.
   * 
   * @pre the_parks != null
   * @pre the_users != null
   * @pre the_jobs != null
   * @pre the_options != null
   * 
   * @post the_parks != null
   * @post the_users != null
   * @post the_jobs != null
   * @post the_options != null
   *
   */
  public String loadData(List<Park> the_parks, List<User> the_users, List<Job> the_jobs, Options the_options){
    /*  Begin conversion of xml document. 
    Referenced http://www.roseindia.net/tutorials/xPath/java-xpath.shtml for ideas
    on how to construct this method. */
    DocumentBuilder builder = null;
    Document doc = null;
    XPathExpression users_expr = null;
    XPathExpression parks_expr = null;
    XPathExpression jobs_expr = null;
    XPathExpression options_expr = null;
    Object users_eval = null;
    Object parks_eval = null;
    Object jobs_eval = null;
    Object options_eval = null;
    InputStream in = null;
    XPath xpath = null;
    String doc_string = null;
    //get xml response and put in document.
    String path_sep = File.pathSeparator;
    File file = new File("parksxml.txt");
    try {
      builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
      in = new FileInputStream(file);
      
      //Parse the document
      InputSource is = new InputSource(new InputStreamReader(in));  
      doc = builder.parse(is);
      
      //Print out the document
      StringWriter out = new StringWriter();
      TransformerFactory.newInstance().newTransformer().transform(new DOMSource(doc), new StreamResult(out));
      System.out.println(out.toString());
      doc_string = out.toString();
      
      xpath = XPathFactory.newInstance().newXPath();
    } catch (Exception e) {
    }
    try {
      //Create expressions
    users_expr = xpath.compile("//xml/users/user");
    parks_expr = xpath.compile("//xml/parks/park");
    jobs_expr = xpath.compile("//xml/jobs/job");
    options_expr = xpath.compile("//xml/options");    
    } catch (XPathExpressionException e) {
    }
    try {
      //Evaluate expressions
      users_eval = users_expr.evaluate(doc, XPathConstants.NODESET);
      parks_eval = parks_expr.evaluate(doc, XPathConstants.NODESET);
      jobs_eval = jobs_expr.evaluate(doc, XPathConstants.NODESET);
      options_eval = options_expr.evaluate(doc, XPathConstants.NODESET);
    } catch (XPathExpressionException e) {
    }
  
    //Populate users list.
    if (users_eval instanceof NodeList) {
      NodeList entryNodes = (NodeList) users_eval;
      int nodelist_length = entryNodes.getLength();
      /* Outer loop - loops through users */
      for (int j = 0; j < nodelist_length; j++) {
        Node currentEntryNode = entryNodes.item(j);
        NamedNodeMap attributes = currentEntryNode.getAttributes();
        User user = new User();

        /* Inner loop - loops through user attributes */
        for (int i = 0; i < attributes.getLength(); i ++) {
          String tag = ((Attr) attributes.item(i)).getName();
          String text_content = ((Attr) attributes.item(i)).getValue();
          /* tag name corresponds to contained attribute */
          if (tag.equalsIgnoreCase("email")) {
            user.setUserEmail(text_content);
          } else if (tag.equalsIgnoreCase("name")) {
            user.setUserName(text_content);
          } else if (tag.equalsIgnoreCase("role")) {
            if (text_content.equalsIgnoreCase("park_manager")) {
              user.setUserRole(Role.PARK_MANAGER);
            } else if (text_content.equalsIgnoreCase("volunteer")) {
              user.setUserRole(Role.VOLUNTEER);
            } else if (text_content.equalsIgnoreCase("staff_member")) {
              user.setUserRole(Role.STAFF_MEMBER);
            } else if (text_content.equalsIgnoreCase("blackballed")) {
              user.setUserRole(Role.BLACKBALLED);
            }
          }
        }
        //Add the user to the users list.
        the_users.add(user);
      }
    }
    
    //Load the parks list.
    if (parks_eval instanceof NodeList) {
      NodeList entryNodes = (NodeList) parks_eval;
      int nodelist_length = entryNodes.getLength();
      /* Outer loop - loops through parks */
      for (int j = 0; j < nodelist_length; j++) {
        Node currentEntryNode = entryNodes.item(j);
        NamedNodeMap attributes = currentEntryNode.getAttributes();
        Park park = new Park();

        /* Inner loop - loops through park attributes */
        for (int i = 0; i < attributes.getLength(); i ++) {
          String tag = ((Attr) attributes.item(i)).getName();
          String text_content = ((Attr) attributes.item(i)).getValue();
          if (tag.equalsIgnoreCase("region")) {
            park.setRegion(text_content);
          } else if (tag.equalsIgnoreCase("name")) {
            park.setName(text_content);
          } else if (tag.equalsIgnoreCase("manager")) {
            /* Find the park manager by email */
            for (User user: the_users) {
              if (user.getUserEmail().equalsIgnoreCase(text_content)) {
                park.setParkManager(user);
                break;
              }
            }
          } else if (tag.equalsIgnoreCase("address")) {
            park.setAddress(text_content);
          } else if (tag.equalsIgnoreCase("district")) {
            park.setDistrict(text_content);
          }
        }
        //Add the park to the parks list.
        the_parks.add(park);
      }
    }
    
    //Load the jobs list
    if (jobs_eval instanceof NodeList) {
      NodeList jobNodes = (NodeList) jobs_eval;
      int nodelist_length = jobNodes.getLength();
      /* Outer loop - loops through jobs */
      for (int j = 0; j < nodelist_length; j++) {
        Node currentJobNode = jobNodes.item(j);
        NamedNodeMap attributes = currentJobNode.getAttributes();
        Job job = new Job();
        
        
        /* Inner loop - loops through job attributes */
        for (int i = 0; i < attributes.getLength(); i++) {
          String tag = ((Attr) attributes.item(i)).getName();
          String text_content = ((Attr) attributes.item(i)).getValue();
          if (tag.equalsIgnoreCase("title")) {
            job.setTitle(text_content);
          } else if (tag.equalsIgnoreCase("jobDescription")) {
            job.setDescription(text_content);
          } else if (tag.equalsIgnoreCase("heavyWorkDescription")) {
             job.setHvWorkDesc(text_content);
          } else if (tag.equalsIgnoreCase("lightWorkDescription")) {
            job.setLtWorkDesc(text_content);
          } else if (tag.equalsIgnoreCase("mediumWorkDescription")) {
            job.setMdWorkDesc(text_content);
          } else if (tag.equalsIgnoreCase("date")) {
            //Get the date in milliseconds since the epoch and create date.
            Date date = new Date(Long.parseLong(text_content));
            job.setDate(date);
          } else if (tag.equalsIgnoreCase("duration")) {
            job.setDuration(Integer.parseInt(text_content));
//          } else if (tag.equalsIgnoreCase("heavyWorkFilled")) {
//            job.setHeavyWorkFilled(Integer.parseInt(text_content));
          } else if (tag.equalsIgnoreCase("heavyWorkNeeded")) {
            job.setHeavyWorkNeeded(Integer.parseInt(text_content));
//          } else if (tag.equalsIgnoreCase("lightWorkFilled")) {
//            job.setLightWorkFilled(Integer.parseInt(text_content));
          } else if (tag.equalsIgnoreCase("lightWorkNeeded")) {
            job.setLightWorkNeeded(Integer.parseInt(text_content));
//          } else if (tag.equalsIgnoreCase("mediumWorkFilled")) {
//            job.setMediumWorkFilled(Integer.parseInt(text_content));
          } else if (tag.equalsIgnoreCase("mediumWorkNeeded")) {
            job.setMediumWorkNeeded(Integer.parseInt(text_content));
          } else if (tag.equalsIgnoreCase("parkName")) {
            for (Park park: the_parks) {
              if (park.getName().equalsIgnoreCase(text_content)) {
                job.setPark(park);
                break;
              }
            }
          } 
        }
        
        NodeList volunteers = currentJobNode.getChildNodes();
        int volunteers_length = volunteers.getLength();
        
        //Get the volunteers with attributes and add them to the job.
        for (int k = 0; k < volunteers_length; k++) {
          Node volunteer = volunteers.item(k);
          NamedNodeMap volunteer_attributes = volunteer.getAttributes();
         
          String vol_email = "";
          String work_load = "";
          for (int l = 0; l < volunteer_attributes.getLength(); l++) {
            String tag = volunteer_attributes.item(l).getNodeName();
            String text_content = volunteer_attributes.item(l).getTextContent();
            if (tag.equalsIgnoreCase("email")) {
              vol_email= text_content;
            } else if (tag.equalsIgnoreCase("workLoad")){
              work_load = text_content;
            }
          }
          for (User user: the_users) {
            if (user.getUserEmail().equalsIgnoreCase(vol_email)) {
              WorkLoad wl = WorkLoad.LIGHT;
              if(work_load.equalsIgnoreCase("MEDIUM")){
                wl = WorkLoad.MEDIUM;
              } else if(work_load.equalsIgnoreCase("HEAVY")){
                wl = WorkLoad.HEAVY;
              }
              job.addVolunteer(user, wl);
              break;
            }
          }
        }

        //Add the job to the jobs list.
        the_jobs.add(job);
      }
    }

    //Populate the options parameter
    if (options_eval instanceof NodeList) {
      NodeList entryNodes = (NodeList) options_eval;
      int nodelist_length = entryNodes.getLength();
      /* Outer loop - loops through workspaces */
      for (int j = 0; j < nodelist_length; j++) {
        Node currentEntryNode = entryNodes.item(j);
        NamedNodeMap attributes = currentEntryNode.getAttributes();
        /* Inner loop - loops through entry attributes */
        for (int i = 0; i < attributes.getLength(); i ++) {
          String tag = ((Attr) attributes.item(i)).getName();
          String text_content = ((Attr) attributes.item(i)).getValue();
          if (tag.equalsIgnoreCase("dataFilePath")) {
            the_options.setDataFilePath(text_content);
          //Note - entry must have path in order to set unique identifier.
          } else if (tag.equalsIgnoreCase("maxJobs")) {
            the_options.setMaxJobs(Integer.parseInt(text_content));
          } else if (tag.equalsIgnoreCase("maxScheduleAheadDays")) {
            the_options.setMaxScheduleAheadDays(Integer.parseInt(text_content));
          } else if (tag.equalsIgnoreCase("maxWeeklyJobs")) {
            the_options.setMaxWeeklyJobs(Integer.parseInt(text_content));
          }
        }
      }
    }
    try {
      in.close();
    } catch (IOException e) {
      // Shouldn't happen
    }
    return doc_string;
  }
 
  
  /**
   * Persists the data.
   *
   * @param the_parks A list of the parks in the system to store.
   * @param the_users A list of the users in the system to store.
   * @param the_jobs A list of the jobs in the system to store.
   * @param the_options The program options to store.
   * 
   * @pre the_parks != null
   * @pre the_users != null
   * @pre the_jobs != null
   * @pre the_options != null
   */
  public String storeData(List<Park> the_parks, List<User> the_users, List<Job> the_jobs, Options the_options){
    Document document = createXmlDocument(the_parks, the_users, the_jobs, the_options);
    String doc_string = "";
    createFile(document, the_options);
    //Print out the document
    StringWriter out = new StringWriter();
    try {
      TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), new StreamResult(out));
    } catch (Exception e) {
      out = null;
    }
    if (out != null) {
      doc_string = out.toString();
    }
    return doc_string;
  }
  
  /*
   * Create the xml document
   * 
   * @pre the_parks != null
   * @pre the_users != null
   * @pre the_jobs != null
   * @pre the_options != null
   * 
   * @post document != null
   * 
   */
  private Document createXmlDocument(List<Park> the_parks, List<User> the_users, List<Job> the_jobs, 
      Options the_options) {
    Document document= null;
    try {
      //Initialize document.
      document = DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument();
    } catch (ParserConfigurationException e) {
      //Shouldn't happen.
    }
    if (document != null) {
      //Create the root tag.
      Element root = document.createElement("xml");
      document.appendChild(root);
      
      //Create the users tag.
      Element users = document.createElement("users");
      root.appendChild(users);
      
      //Create the parks tag.
      Element parks = document.createElement("parks");
      root.appendChild(parks);
      
      //Create the jobs tag.
      Element jobs = document.createElement("jobs");
      root.appendChild(jobs);
      
      //Create the options tag with attributes.
      Element options = document.createElement("options");
      options.setAttribute("dataFilePath", the_options.getDataFilePath());
      options.setAttribute("maxJobs", "" + the_options.getMaxJobs());
      options.setAttribute("maxScheduleAheadDays", "" + the_options.getMaxScheduleAheadDays());
      options.setAttribute("maxWeeklyJobs", "" + the_options.getMaxWeeklyJobs());
      root.appendChild(options);
      
      //Create user tags along with attributes.
      for (User user: the_users) {
        Element user_element = document.createElement("user");
        user_element.setAttribute("email", user.getUserEmail());
        user_element.setAttribute("name", user.getUserName());
        user_element.setAttribute("role", user.getUserRole().toString());
        users.appendChild(user_element);
      }
      
      //Create park tags along with attributes.
      for (Park park: the_parks) {
        Element park_element = document.createElement("park");
        park_element.setAttribute("district", park.getDistrict());
        park_element.setAttribute("region", park.getRegion());
        park_element.setAttribute("name", park.getName());
        park_element.setAttribute("manager", park.getParkManager().getUserEmail());
        park_element.setAttribute("address", park.getAddress());
        parks.appendChild(park_element);
      }
      
      //Create Job tags along with attributes, Volunteer tags and attributes.
      for (Job job: the_jobs) {
        //Create the job tag and its elements
        Element job_element = document.createElement("job");
        job_element.setAttribute("title", job.getTitle());
        job_element.setAttribute("jobDescription", job.getDescription());
        job_element.setAttribute("heavyWorkDescription", job.getHeavyWorkDescription());
        job_element.setAttribute("lightWorkDescription", job.getLightWorkDescription());
        job_element.setAttribute("mediumWorkDescription", job.getMediumWorkDescription());
        //Convert date to milliseconds since last epoch.
        String milliseconds = new Long(job.getDate().getTime()).toString();
        job_element.setAttribute("date", milliseconds);
        job_element.setAttribute("duration", "" + job.getDuration());
        //job_element.setAttribute("heavyWorkFilled", "" + job.getHeavyWorkFilled());
        job_element.setAttribute("heavyWorkNeeded", "" + job.getHeavyWorkNeeded());
        //job_element.setAttribute("lightWorkFilled", "" + job.getLightWorkFilled());
        job_element.setAttribute("lightWorkNeeded", "" + job.getLightWorkNeeded());
        //job_element.setAttribute("mediumWorkFilled", "" + job.getMediumWorkFilled());           
        job_element.setAttribute("mediumWorkNeeded", "" + job.getMediumWorkNeeded());                
        job_element.setAttribute("parkName", job.getPark().getName()); 
        
        //Append volunteers and their attributes.
        
        for (User volunteer: job.getVolunteers().keySet()) {
          Element volunteer_element = document.createElement("volunteer");
          volunteer_element.setAttribute("email", volunteer.getUserEmail());
          volunteer_element.setAttribute("workLoad", WorkLoad.toString((WorkLoad)job.getVolunteers().get(volunteer))); 
          job_element.appendChild(volunteer_element);
        }
        
        //Append job tag
        jobs.appendChild(job_element);
      }
      
      //Append jobs tag
      root.appendChild(jobs);
    }
    return document;
  }
  
  /*
   * Output the document to disk.
   * 
   * @pre the_document != null
   * @pre the_options != null
   */
  private void createFile(Document the_document, Options the_options) {
    File file = null;
    try {
      Source source = new DOMSource(the_document);
      file = new File(the_options.getDataFilePath());
      Result result = new StreamResult(file);
      Transformer transformer = TransformerFactory.newInstance().newTransformer();
      transformer.transform(source, result);
    } catch (TransformerConfigurationException e) {
      file = null;
    } catch (TransformerException e) {
      file = null;
    }
  }
}
